assert(ThePlayer.HUD and TheWorld, "使用Pusher组件必须保证ThePlayer和TheWorld存在！")
local t_util = require "libs/tableutil"
local p_util = require "libs/playerutil"
local w_util = require "libs/worldutil"
local e_util = require "libs/entutil"


local invs = {}             -- 根据物品查是否被移除
local noteitems = {}        -- 根据位置查是否有物品
-- 我写的第一个组件！
-- 此组件用来推送一些自定义事件
local HxInv = Class(function(self)
    self.last_unequip = nil
    invs = {}
    noteitems = {}
    self:StartTrack()
end)

-- 记录物品位置
local function SetNoteItem(cont, slot, item)
    local guid = cont and cont.GUID
    if guid and slot then
        noteitems[guid..slot] = item
    end
end
-- 获取物品位置
local function GetNoteItem(cont, slot)
    local guid = cont and cont.GUID
    if guid and slot then
        return noteitems[guid..slot]
    end
end

function HxInv:RegItemget(item, cont, slot)
    SetNoteItem(cont, slot, item)
    local guid = item and item.GUID
    if guid and not invs[guid] then
        ThePlayer:PushEvent("itemget_withhx", {
            cont = cont,
            item = item,
            slot = slot
        })
        invs[guid] = item
        self:TrackContainer(item)
    end
end

function HxInv:RegItemlose(item, cont, slot)
    SetNoteItem(cont, slot)
    local guid = item and item.GUID
    if guid and invs[guid] then
        ThePlayer:PushEvent("itemlose_withhx", {
            cont = cont,
            item = item,
            slot = slot
        })
        invs[guid] = nil
    end
end
local function PushEat(food)
    if food and food.prefab then
        ThePlayer:PushEvent("oneat_withhx", food)
    end
end
-- 事件：吃掉
function HxInv:RegOnEat(food)
    PushEat(food)
end
-- 事件：打断操作（通过点击或者按下移动键或者排队论启动）
function HxInv:RegBreakCtrl()
    ThePlayer:PushEvent("breakctrl_withhx")
end
function HxInv:RegMoveCtrl()
    ThePlayer:PushEvent("movectrl_withhx")
end
-- 事件：装备
function HxInv:RegEquip(slot, item)
    self:RegUnequip(slot, item)
    ThePlayer:PushEvent("equip_withhx", {
        item = item,
        slot = slot
    })
    self:RegItemget(item, ThePlayer, slot)
end


-- 怀疑物品丢失
-- meta 设置 prev_item 则直接作为item（主机模式）
-- 未设置时根据 slot 获得 （客机获得）
local function ItemloseToDo(cont, meta)
    local item = meta and meta.prev_item
    local slot = meta and meta.slot
    item = item or GetNoteItem(cont, slot)
    if not item then return end
    cont:DoTaskInTime(0, function()
        if not e_util:IsValid(item) or not item:HasTag("inlimbo") then
            HxInv:RegItemlose(item, cont, slot)
        end
    end)
end
-- 事件：卸载(传入的item并没有任何用)
function HxInv:RegUnequip(slot, item)
    if slot then
        local item = GetNoteItem(ThePlayer, slot)
        if item then
            ThePlayer:PushEvent("unequip_withhx", {
                item = item,
                slot = slot,
            })
            ItemloseToDo(ThePlayer, {slot = slot})
            self.last_unequip = item
        end
    end
end

-- 最近脱落的装备
function HxInv:GetLastUnequip()
    return self.last_unequip
 end


local function ItemgetToDo(cont, meta)
    if meta.item then
        HxInv:RegItemget(meta.item, cont, meta.slot)
    end
end


function HxInv:TrackContainer(cont)
    local container = e_util:GetContainer(cont)
    if not container then return end
    e_util:SetBindEvent(cont, "itemget", ItemgetToDo)
    e_util:SetBindEvent(cont, "itemlose", ItemloseToDo)
end

-- 有必要再推一个装备销毁？
local function OnRemoveEvent(item)
    local slot = e_util:GetItemEquipSlot(item)
    if slot and item == GetNoteItem(ThePlayer, slot) then
        HxInv:RegUnequip(slot, item)
    end
end
-- 饥饿为空时推送（伴随掉血也会不停的推）
-- local function RegInhunger(player)
--     local hunger = player.replica and player.replica.hunger and player.replica.hunger:GetCurrent() <= 0
--     if hunger then
--         player:PushEvent("inhunger_withhx")
--     end
-- end


-- 黑暗事件，该事件每秒都会推送一次！
function HxInv:RegInDark(event)
    -- true       -- 天黑：持续推送
    -- false      -- 变的天亮：变亮时推一次
    -- 持续有光源的时候不推送
    ThePlayer:PushEvent("indark_withhx", event)
end
-- 位置事件，返回不是精确的玩家位置，每秒一次！
function HxInv:RegPosition(pos)
    -- {x=.2f z=.2f}
    ThePlayer:PushEvent("position_withhx", pos)
end
-- 挂机事件，完成下方setlistener注册后，将会在挂机此时间时推送事件
function HxInv:SetListenerASleep(time)
    if type(time) == "number" and time>2 then
        self.asleep_time = time
    else
        self.asleep_time = nil
    end
end
function HxInv:RegASleep(time)
    ThePlayer:PushEvent("asleep_withhx", time)
end
local interrupt_controls = {}
for control = CONTROL_ATTACK, CONTROL_MOVE_RIGHT do
    interrupt_controls[control] = true
end
local mouse_controls = {[CONTROL_PRIMARY] = true, [CONTROL_SECONDARY] = true}
function HxInv:StartTrack()
    -- 装备事件
    t_util:Pairs(EQUIPSLOTS, function(_,slot)
        local item = p_util:GetEquip(slot) 
        if item then self:RegEquip(slot, item) end
    end)
    ThePlayer:ListenForEvent("equip", function(_, meta)
        local slot, item = meta.eslot, meta.item
        if slot and item then
            e_util:SetBindEvent(item, "onremove", OnRemoveEvent)
            self:RegEquip(slot, item)
        end
    end)
    ThePlayer:ListenForEvent("unequip", function (_, meta)
        self:RegUnequip(meta.eslot, meta.item)
    end)


    -- 物品事件
    t_util:Pairs(p_util:GetSlotsFromAll(), function(item, slots)
        self:RegItemget(item, slots.container, slots.slot)
    end) 

    -- 开箱动作发生后即可获取里面的物品
    -- 关箱动作发生前无法获取里面的物品
    local HUD = ThePlayer.HUD
    local _OpenContainer = HUD.OpenContainer
    HUD.OpenContainer = function(HUD, cont_inst, ...)
        local result = _OpenContainer(HUD, cont_inst, ...)
        local cont = e_util:GetContainer(cont_inst)
        if cont then
            self:TrackContainer(cont_inst)  -- 仅开箱时追踪
            t_util:Pairs(cont:GetItems(), function(slot, item)
                self:RegItemget(item, cont_inst, slot)
            end)
        end
        return result
    end
    local _CloseContainer = HUD.CloseContainer
    HUD.CloseContainer = function(HUD, cont_inst, ...)
        local cont = e_util:GetContainer(cont_inst)
        local result = _CloseContainer(HUD, cont_inst, ...)
        if cont then
            for slot = 1,cont:GetNumSlots() do
                self:RegItemlose(GetNoteItem(cont_inst, slot), cont_inst, slot)
            end
        end
        return result
    end

    self:TrackContainer(ThePlayer)
    ThePlayer:ListenForEvent("newactiveitem", function(player, meta)
        local item = meta and meta.item
        if item then
            self:RegItemget(item, player, "mouse")
        else
            ItemloseToDo(player, {slot = "mouse"})
        end
    end)


    -- 饥饿事件
    -- ThePlayer:ListenForEvent("hungerdelta", RegInhunger)
    -- ThePlayer:ListenForEvent("healthdelta", RegInhunger)

    local lh = {1, 1, 1}
    local x_last, z_last, count_asleep = 0, 0, 0
    ThePlayer:DoPeriodicTask(1, function(player)
        -- 黑暗事件
        local lightvalue = p_util:GetLightValue()
        if lightvalue then
            local lw = player.LightWatcher
            lh[1] = lh[2] lh[2] = lh[3] lh[3] = lightvalue
            if lightvalue == 0 or not lw:IsInLight() then
                self:RegInDark(true)
            else
                if lh[3]>lh[2] and lh[2]>lh[1] then
                    self:RegInDark(false)
                end
            end
        end
        -- 位置事件
        if player and player.Transform then
            local x,_,z = player.Transform:GetWorldPosition()
            if x and z then
                -- 挂机事件
                local astime = self.asleep_time
                if type(astime) == "number" then
                    if x_last==x and z_last==z then
                        count_asleep = count_asleep + 1
                    else
                        count_asleep = 0
                    end 
                    x_last,z_last = x,z
                    if count_asleep > astime then
                        self:RegASleep(count_asleep)
                        count_asleep = 0
                    end
                end

                self:RegPosition({
                    x = string.format("%.2f", x),
                    z = string.format("%.2f", z)
                })
            end
        end
    end)
    
    
    -- 按下打断键
    -- 注意：这里可以扩展，检测按下任意键，如有需求再添加
    ThePlayer:ListenForEvent("aqp_threadstart", self.RegBreakCtrl)
    local pc = ThePlayer.components.playercontroller
    if pc then
        local _OnControl = pc.OnControl
        pc.OnControl = function(pc, control, down)
            if down then
                local mouse_control = mouse_controls[control]
                local interrupt_control = interrupt_controls[control]
                if e_util:InGame() then
                    if (interrupt_control or mouse_control and not TheInput:IsControlPressed(CONTROL_FORCE_INSPECT)) then
                        if interrupt_control then
                            self:RegMoveCtrl()
                        end
                        self:RegBreakCtrl()
                    end
                end
            end
            return _OnControl(pc, control, down)
        end

        -- local _DoAction = pc.DoAction
        -- pc.DoAction = function(pc, act, ...)
        --     local code = act and act.action and act.action.code
        --     print("DoAction", act.action, act.action == ACTIONS.EAT)
        --     return _DoAction(pc, act, ...)
        -- end
    end

    if TheWorld.ismastersim then
        ThePlayer:ListenForEvent("oneat", function(_, data)
            self:RegOnEat(data.food)
        end)
    end
end


local _SendRPCToServer = SendRPCToServer
function SendRPCToServer(rpc_code, act_code, arg1, ...)
    if act_code == ACTIONS.EAT.code then
        local food = type(arg1)=="number" and p_util:GetActiveItem() or arg1
        if e_util:IsValid(food) then
            local size = e_util:GetStacksize(food)
            ThePlayer:DoTaskInTime(1, function()
                if e_util:IsValid(food)  then
                    if size ~= e_util:GetStacksize(food) then
                        PushEat(food)
                    end
                else
                    PushEat(food)
                end
            end)
        end
    end
    return _SendRPCToServer(rpc_code, act_code, arg1, ...)
end

-- 有洞穴：
-- 开关延迟：直接吃：RPC
-- 关延迟：自动：RPC 开延迟：ACTION+RPC
-- 无洞穴
-- 开关延迟，直接吃：EVENT
-- 自动：开关延迟：ACTION+EVENT


return HxInv